3172
3597
Les livres de langage de programmation expliquent que les types valeur sont créés sur la pile et que les types référence sont créés sur le tas, sans expliquer ce que sont ces deux choses. Je n'ai pas lu d'explication claire à ce sujet. Je comprends ce qu'est une pile. Mais,
Où et quels sont-ils (physiquement dans la mémoire d'un vrai ordinateur)?
Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou le langage d'exécution?
Quelle est leur portée?
Qu'est-ce qui détermine la taille de chacun d'eux?
Qu'est-ce qui rend plus rapide? 
La pile est la mémoire mise de côté comme espace de travail pour un thread d'exécution. Lorsqu'une fonction est appelée, un bloc est réservé en haut de la pile pour les variables locales et certaines données comptables. Lorsque cette fonction revient, le bloc devient inutilisé et peut être utilisé la prochaine fois qu'une fonction est appelée. La pile est toujours réservée dans un ordre LIFO (dernier entré premier sorti); le bloc le plus récemment réservé est toujours le prochain bloc à libérer. Cela rend très simple le suivi de la pile; libérer un bloc de la pile n'est rien de plus que d'ajuster un pointeur.
Le tas est de la mémoire réservée à l'allocation dynamique. Contrairement à la pile, il n'y a pas de modèle imposé pour l'allocation et la désallocation des blocs du tas; vous pouvez allouer un bloc à tout moment et le libérer à tout moment. Cela rend beaucoup plus complexe le suivi des parties du tas allouées ou libres à un moment donné; il existe de nombreux allocateurs de tas personnalisés disponibles pour régler les performances du tas pour différents modèles d'utilisation.
Chaque thread obtient une pile, alors qu'il n'y a généralement qu'un seul tas pour l'application (bien qu'il ne soit pas rare d'avoir plusieurs tas pour différents types d'allocation).
Pour répondre directement à vos questions:
Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou l'environnement d'exécution du langage?
Le système d'exploitation alloue la pile pour chaque thread de niveau système lorsque le thread est créé. En général, le système d'exploitation est appelé par le moteur d'exécution du langage pour allouer le segment de mémoire à l'application.
Quelle est leur portée?
La pile est attachée à un thread, donc lorsque le thread sort, la pile est récupérée. Le tas est généralement alloué au démarrage de l'application par le runtime et est récupéré lorsque l'application (techniquement processus) se termine.
Qu'est-ce qui détermine la taille de chacun d'eux?
La taille de la pile est définie lors de la création d'un thread. La taille du tas est définie au démarrage de l'application, mais peut augmenter en fonction de l'espace nécessaire (l'allocateur demande plus de mémoire au système d'exploitation).
Qu'est-ce qui rend un plus rapide?
La pile est plus rapide car le modèle d'accès rend facile d'allouer et de désallouer de la mémoire (un pointeur / entier est simplement incrémenté ou décrémenté), tandis que le tas a une comptabilité beaucoup plus complexe impliquée dans une allocation ou une désallocation. De plus, chaque octet de la pile a tendance à être réutilisé très fréquemment, ce qui signifie qu'il a tendance à être mappé vers le cache du processeur, ce qui le rend très rapide. Un autre problème de performances pour le tas est que le tas, qui est principalement une ressource globale, doit généralement être sûr pour le multi-threading, c'est-à-dire que chaque allocation et désallocation doit être - généralement - synchronisée avec "tous" les autres accès au tas du programme.
Une démonstration claire:
Source de l'image: vikashazrati.wordpress.com
|
Empiler:
Stocké dans la RAM de l'ordinateur, tout comme le tas.
Les variables créées sur la pile sortiront du champ d'application et seront automatiquement désallouées.
Beaucoup plus rapide à allouer par rapport aux variables sur le tas.
Implémenté avec une structure de données de pile réelle.
Stocke les données locales, les adresses de retour, utilisées pour le passage des paramètres.
Peut avoir un débordement de pile lorsque trop de pile est utilisée (principalement à partir d'une récursion infinie ou trop profonde, d'allocations très importantes).
Les données créées sur la pile peuvent être utilisées sans pointeurs.
Vous utiliseriez la pile si vous savez exactement combien de données vous devez allouer avant la compilation et que ce n'est pas trop gros.
A généralement une taille maximale déjà déterminée au démarrage de votre programme.
Tas:
Stocké dans la RAM de l'ordinateur tout comme la pile.
En C ++, les variables sur le tas doivent être détruites manuellement et ne jamais tomber hors de portée. Les données sont libérées avec delete, delete [] ou free.
Plus lent à allouer par rapport aux variables de la pile.
Utilisé à la demande pour allouer un bloc de données à utiliser par le programme.
Peut avoir une fragmentation lorsqu'il y a beaucoup d'allocations et de désallocations.
En C ++ ou C, les données créées sur le tas seront pointées par des pointeurs et allouées respectivement avec new ou malloc.
Peut avoir des échecs d'allocation si une trop grande mémoire tampon est demandée pour être allouée.
Vous utiliseriez le tas si vous ne savez pas exactement combien de données vous aurez besoin au moment de l'exécution ou si vous avez besoin d'allouer beaucoup de données.
Responsable des fuites de mémoire.
Exemple:
int toto ()
{
char * pBuffer; // <- rien n'est encore alloué (à l'exception du pointeur lui-même, qui est alloué ici sur la pile).
booléen b = vrai; // Alloué sur la pile.
si (b)
{
// Créer 500 octets sur la pile
tampon de caractères [500];
// Créer 500 octets sur le tas
pBuffer = nouveau caractère [500];
} // <- le tampon est libéré ici, pBuffer ne l'est pas
} // <--- oups il y a une fuite de mémoire, j'aurais dû appeler delete [] pBuffer;
|
Le point le plus important est que tas et pile sont des termes génériques désignant les moyens d'allouer la mémoire. Ils peuvent être mis en œuvre de différentes manières et les termes s'appliquent aux concepts de base.
Dans une pile d'éléments, les éléments sont placés les uns sur les autres dans l'ordre dans lequel ils ont été placés, et vous ne pouvez supprimer que celui du haut.(sans renverser le tout).
La simplicité d'une pile est que vous n'avez pas besoin de maintenir une table contenant un enregistrement de chaque section de mémoire allouée; la seule information d'état dont vous avez besoin est un pointeur unique vers la fin de la pile. Pour allouer et désallouer, il vous suffit d'incrémenter et de décrémenter ce pointeur unique. Remarque: une pile peut parfois être implémentée pour démarrer en haut d'une section de mémoire et s'étendre vers le bas plutôt que de croître vers le haut.
Dans un tas, il n'y a pas d'ordre particulier dans la façon dont les éléments sont placés. Vous pouvez accéder aux articles et les supprimer dans n'importe quel ordre, car il n'y a pas d'élément «principal» clair.
L'allocation de tas nécessite de conserver un enregistrement complet de la mémoire allouée et de ce qui ne l'est pas, ainsi que d'une maintenance supplémentaire pour réduire la fragmentation, trouver des segments de mémoire contigus suffisamment grands pour s'adapter à la taille demandée, etc. La mémoire peut être désallouée à tout moment en laissant de l'espace libre. Parfois, un allocateur de mémoire effectuera des tâches de maintenance telles que la défragmentation de la mémoire en déplaçant la mémoire allouée ou le garbage collection - identifiant au moment de l'exécution lorsque la mémoire n'est plus dans la portée et la désallouant.
Ces images devraient faire un assez bon travail pour décrire les deux façons d'allouer et de libérer de la mémoire dans une pile et un tas. Miam!
Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou l'environnement d'exécution du langage?
Comme mentionné, tas et pile sont des termes généraux et peuvent être implémentés de plusieurs manières. Les programmes informatiques ont généralement une pile appelée pile d'appels qui stocke des informations relatives à la fonction en cours, comme un pointeur vers la fonction à partir de laquelle il a été appelé et toutes les variables locales. Étant donné que les fonctions appellent d'autres fonctions puis reviennent, la pile s'agrandit et se réduit pour contenir les informations des fonctions situées plus loin dans la pile d'appels. Un programme n'a pas vraiment de contrôle d'exécution dessus; il est déterminé par le langage de programmation, le système d'exploitation et même l'architecture du système.
Un tas est un terme général utilisé pour toute mémoire allouée de manière dynamique et aléatoire; c'est-à-dire hors service. La mémoire est généralement allouée par le système d'exploitation, l'application appelant des fonctions API pour effectuer cette allocation. La gestion de la mémoire allouée dynamiquement est assez lourde, ce qui est généralement géré par le code d'exécution du langage de programmation ou de l'environnement utilisé.
Quelle est leur portée?
La pile d'appels est un concept de si bas niveau qu'elle ne se rapporte pas à la «portée» au sens de la programmation. Si vous démontez du code, vous verrez des références de style de pointeur relatives à des parties de la pile, mais en ce qui concerne un langage de niveau supérieur, le langage impose ses propres règles de portée. Cependant, un aspect important d'une pile est qu'une fois qu'une fonction est renvoyée, tout ce qui est local à cette fonction est immédiatement libéré de la pile. Cela fonctionne comme vous vous attendez à ce que cela fonctionne étant donné le fonctionnement de vos langages de programmation. Dans un tas, c'est également difficile à définir. La portée est tout ce qui est exposé par le système d'exploitation, mais votre langage de programmation ajoute probablement ses règles sur ce qu'est une "portée" dans votre application. L'architecture du processeur et le système d'exploitation utilisent l'adressage virtuel, que le processeur traduit en adresses physiques et il y a des défauts de page, etc. Ils gardent une trace de quelles pages appartiennent à quelles applications. Cependant, vous n'avez jamais vraiment besoin de vous en soucier, car vous utilisez simplement la méthode utilisée par votre langage de programmation pour allouer et libérer de la mémoire, et vérifiez les erreurs (si l'allocation / la libération échoue pour une raison quelconque).
Qu'est-ce qui détermine la taille de chacun d'eux?
Encore une fois, cela dépend du langage, du compilateur, du système d'exploitation et de l'architecture. Une pile est généralement pré-allouée, car par définition, elle doit être contiguë à la mémoire. Le compilateur de langage ou le système d'exploitation déterminent sa taille. Vous ne stockez pas d'énormes morceaux de données sur la pile, donc elle sera suffisamment grande pour ne jamais être pleinement utilisée, sauf en cas de récursivité sans fin indésirable (d'où le «débordement de pile») ou d'autres décisions de programmation inhabituelles.
Un tas est un terme général pour tout ce qui peut être alloué dynamiquement. Selon la façon dont vous le regardez, sa taille change constamment. Dans les processeurs et les systèmes d'exploitation modernes, la manière exacte dont cela fonctionne est de toute façon très abstraite, vous n'avez donc normalement pas besoin de vous soucier de son fonctionnement en profondeur, sauf que (dans les langues où cela vous permet) vous ne devez pas utiliser vous n'avez pas encore alloué ou la mémoire que vous avez libérée.
Qu'est-ce qui rend un plus rapide?
La pile est plus rapide car toute la mémoire libre est toujours contiguë. Aucune liste de tous les segments de mémoire libre ne doit être conservée, juste un seul pointeur vers le haut actuel de la pile. Les compilateurs stockent généralement ce pointeur dans un registre rapide spécial à cet effet. De plus, les opérations ultérieures sur une pile sont généralement concentrées dans des zones de mémoire très proches, ce qui à un niveau très bas est bon pour l'optimisation par le processeur sur la puce.caches.
|
(J'ai déplacé cette réponse d'une autre question qui était plus ou moins une dupe de celle-ci.)
La réponse à votre question est spécifique à l'implémentation et peut varier selon les compilateurs et les architectures de processeurs. Cependant, voici une explication simplifiée.
La pile et le tas sont des zones de mémoire allouées à partir du système d'exploitation sous-jacent (souvent de la mémoire virtuelle mappée à la mémoire physique à la demande).
Dans un environnement multi-thread, chaque thread aura sa propre pile complètement indépendante mais partagera le tas. L'accès simultané doit être contrôlé sur le tas et n'est pas possible sur la pile.
Le tas
Le tas contient une liste chaînée de blocs utilisés et libres. Les nouvelles allocations sur le tas (par new ou malloc) sont satisfaites en créant un bloc approprié à partir de l'un des blocs libres. Cela nécessite la mise à jour de la liste des blocs sur le tas. Ces méta-informations sur les blocs sur le tas sont également stockées sur le tas souvent dans une petite zone juste en face de chaque bloc.
Au fur et à mesure que le tas se développe, de nouveaux blocs sont souvent alloués des adresses inférieures vers les adresses supérieures. Ainsi, vous pouvez considérer le tas comme un tas de blocs de mémoire dont la taille augmente à mesure que la mémoire est allouée. Si le tas est trop petit pour une allocation, la taille peut souvent être augmentée en acquérant plus de mémoire du système d'exploitation sous-jacent.
L'allocation et la désallocation de nombreux petits blocs peuvent laisser le tas dans un état où il y a beaucoup de petits blocs libres intercalés entre les blocs utilisés. Une demande d'allocation d'un grand bloc peut échouer car aucun des blocs libres n'est assez grand pour satisfaire la demande d'allocation même si la taille combinée des blocs libres peut être suffisamment grande. C'est ce qu'on appelle la fragmentation du tas.
Lorsqu'un bloc utilisé qui est adjacent à un bloc libre est désalloué, le nouveau bloc libre peut être fusionné avec le bloc libre adjacent pour créer un bloc libre plus grand, réduisant efficacement la fragmentation du tas.
La pile
La pile fonctionne souvent en tandem étroit avec un registre spécial sur le processeur appelé le pointeur de pile. Au départ, le pointeur de pile pointe vers le haut de la pile (l'adresse la plus élevée de la pile).
Le CPU a des instructions spéciales pour pousser les valeurs sur la pile et les faire sortir de la pile. Chaque poussée stocke la valeur à l'emplacement actuel du pointeur de pile et diminue le pointeur de pile. Un pop récupère la valeur pointée par le pointeur de pile puis augmente le pointeur de pile (ne soyez pas confus par le fait que l'ajout d'une valeur à la pile diminue le pointeur de pile et la suppression d'une valeur l'augmente. le fond). Les valeurs stockées et récupérées sont les valeurs des registres CPU.
Lorsqu'une fonction est appelée, la CPU utilise des instructions spéciales qui poussent le pointeur d'instruction actuel, c'est-à-dire l'adresse du code s'exécutant sur la pile. La CPU passe alors à la fonction en réglant le
pointeur d'instruction vers l'adresse de la fonction appelée. Plus tard, lorsque la fonction revient, l'ancien pointeur d'instruction est extrait de la pile et l'exécution reprend au niveau du code juste après l'appel de la fonction.
Lorsqu'une fonction est entrée, le pointeur de pile est diminué pour allouer plus d'espace sur la pile pour les variables locales (automatiques). Si la fonction a une variable locale de 32 bits, quatre octets sont mis de côté sur la pile. Lorsque la fonction retourne, le pointeur de pile est déplacé vers l'arrière pour libérer la zone allouée.
Si une fonction a des paramètres, ceux-ci sont poussés sur la pile avant l'appel de la fonction. Le code de la fonction est alors capable de parcourir la pile à partir du pointeur de pile actuel pour localiser ces valeurs.
Les appels de fonction d'imbrication fonctionnent comme un charme. Chaque nouvel appel allouera des paramètres de fonction, l'adresse de retour et l'espace pour les variables locales et ces enregistrements d'activation peuvent être empilés pour les appels imbriqués et se dérouleront de la manière correcte lorsque les fonctions reviendront.
Comme la pile est un bloc limité de mémoire, vous pouvez provoquer un débordement de pile en appelant trop de fonctions imbriquées et / ou en allouant trop d'espace pour les variables locales. Souvent, la zone de mémoire utilisée pour la pile est configurée de telle manière que l'écriture sous le bas (l'adresse la plus basse) de la pile déclenchera une interruption ou une exception dans le CPU. Cette condition exceptionnelle peut ensuite être interceptée par le runtime et convertie en une sorte d'exception de débordement de pile.
Une fonction peut-elle être allouée sur le tas au lieu d'une pile?
Non, les enregistrements d'activation des fonctions (c'est-à-dire des variables locales ou automatiques) sont alloués sur la pile qui est utilisée non seulement pour stocker ces variables, mais également pour garder une trace des appels de fonction imbriqués.
La façon dont le tas est géré dépend vraiment de l'environnement d'exécution. C utilise malloc et C ++ utilise new, mais de nombreux autres langages ont un garbage collection.
Cependant, la pile est une fonctionnalité de plus bas niveau étroitement liée à l'architecture du processeur. Faire grandir le tas lorsqu'il n'y a pas assez d'espace n'est pas trop difficile caril peut être implémenté dans l'appel de bibliothèque qui gère le tas. Cependant, la croissance de la pile est souvent impossible car le débordement de pile n'est découvert que lorsqu'il est trop tard; et l'arrêt du thread d'exécution est la seule option viable.
|
Dans le code C # suivant
public void Méthode1 ()
{
int i = 4;
int y = 2;
class1 cls1 = nouvelle classe1 ();
}
Voici comment la mémoire est gérée
Variables locales qui ne doivent durer que pendant la durée de l'appel de la fonction dans la pile. Le tas est utilisé pour les variables dont nous ne connaissons pas vraiment la durée de vie, mais nous nous attendons à ce qu'elles durent un certain temps. Dans la plupart des langages, il est essentiel de savoir au moment de la compilation la taille d'une variable si nous voulons la stocker sur la pile.
Les objets (qui varient en taille au fur et à mesure que nous les mettons à jour) vont sur le tas car nous ne savons pas au moment de la création combien de temps ils vont durer. Dans de nombreuses langues, le tas est garbage collection pour trouver des objets (tels que l'objet cls1) qui n'ont plus de références.
En Java, la plupart des objets vont directement dans le tas. Dans des langages comme C / C ++, les structures et les classes peuvent souvent rester sur la pile lorsque vous ne traitez pas avec des pointeurs.
Plus d'informations peuvent être trouvées ici:
La différence entre l'allocation de mémoire de pile et de tas «timmurphy.org
et ici:
Création d'objets sur la pile et le tas
Cet article est la source de l'image ci-dessus: Six concepts .NET importants: pile, tas, types de valeur, types de référence, boxing et unboxing - CodeProject
mais sachez qu'il peut contenir des inexactitudes.
|
La pile
Lorsque vous appelez une fonction, les arguments de cette fonction plus une autre surcharge sont placés sur la pile. Certaines informations (comme où aller au retour) y sont également stockées.
Lorsque vous déclarez une variable dans votre fonction, cette variable est également allouée sur la pile.
Désallouer la pile est assez simple car vous désallouez toujours dans l'ordre inverse dans lequel vous allouez. Des éléments de pile sont ajoutés lorsque vous entrez dans des fonctions, les données correspondantes sont supprimées lorsque vous les quittez. Cela signifie que vous avez tendance à rester dans une petite région de la pile, sauf si vous appelez de nombreuses fonctions qui appellent beaucoup d'autres fonctions (ou créez une solution récursive).
Le tas
Le tas est un nom générique pour l'endroit où vous placez les données que vous créez à la volée. Si vous ne savez pas combien de vaisseaux spatiaux votre programme va créer, vous utiliserez probablement l'opérateur new (ou malloc ou équivalent) pour créer chaque vaisseau spatial. Cette allocation va rester pendant un certain temps, il est donc probable que nous libérerons les choses dans un ordre différent de celui dans lequel nous les avons créées.
Ainsi, le tas est beaucoup plus complexe, car il y a des régions de mémoire inutilisées entrelacées avec des morceaux qui sont - la mémoire se fragmente. Trouver de la mémoire libre de la taille dont vous avez besoin est un problème difficile. C'est pourquoi le tas doit être évité (bien qu'il soit encore souvent utilisé).
la mise en oeuvre
L'implémentation à la fois de la pile et du tas dépend généralement de l'exécution / du système d'exploitation. Souvent, les jeux et autres applications critiques pour les performances créent leurs propres solutions de mémoire qui récupèrent une grande partie de la mémoire du tas, puis la distribuent en interne pour éviter de dépendre du système d'exploitation pour la mémoire.
Cela n'est pratique que si votre utilisation de la mémoire est assez différente de la norme - c'est-à-dire pour les jeux où vous chargez un niveau en une seule opération énorme et pouvez jeter le tout dans une autre opération énorme.
Emplacement physique en mémoire
Ceci est moins pertinent que vous ne le pensez à cause d'une technologie appelée mémoire virtuelle qui fait penser à votre programme que vous avez accès à une certaine adresse où les données physiques sont ailleurs (même sur le disque dur!). Les adresses que vous obtenez pour la pile sont dans un ordre croissant à mesure que votre arbre d'appels s'approfondit. Les adresses pour le tas ne sont pas prévisibles (c'est-à-dire spécifiques à l'implantation) et franchement pas importantes.
|
Pour clarifier, cette réponse a des informations incorrectes (Thomas a corrigé sa réponse après des commentaires, cool :)). D'autres réponses évitent simplement d'expliquer ce que signifie l'allocation statique. Je vais donc expliquer les trois principales formes d'allocation et comment elles sont généralement liées au segment de tas, de pile et de données ci-dessous. Je vais également montrer quelques exemples en C / C ++ et Python pour aider les gens à comprendre.
Les variables "statiques" (AKA allouées statiquement) ne sont pas allouées sur la pile. Ne le supposez pas - beaucoup de gens ne le font que parce que «statique» ressemble beaucoup à «pile». Ils n'existent en réalité ni dans la pile ni dans le tas. Ils font partie de ce qu'on appelle le segment de données.
Cependant, il est généralement préférable de considérer "portée" et "durée de vie" plutôt que "pile" et "tas".
La portée fait référence aux parties du code qui peuvent accéder à une variable. En général, nous pensons à la portée locale (accessible uniquement par la fonction actuelle) par rapport à la portée globale (accessible n'importe où), bien que la portée puisse devenir beaucoup plus complexe.
La durée de vie fait référence au moment où une variable est allouée et désallouée pendant l'exécution du programme. On pense généralement à l'allocation statique (variablepersistera pendant toute la durée du programme, ce qui le rend utile pour stocker les mêmes informations sur plusieurs appels de fonction) par rapport à l'allocation automatique (la variable ne persiste que pendant un seul appel à une fonction, ce qui la rend utile pour stocker des informations qui ne sont utilisées que fonction et peut être supprimée une fois que vous avez terminé) par rapport à l'allocation dynamique (variables dont la durée est définie au moment de l'exécution, au lieu de la compilation comme statique ou automatique).
Bien que la plupart des compilateurs et interprètes implémentent ce comportement de la même manière en termes d'utilisation de piles, de tas, etc., un compilateur peut parfois casser ces conventions s'il le souhaite tant que le comportement est correct. Par exemple, en raison de l'optimisation, une variable locale peut uniquement exister dans un registre ou être entièrement supprimée, même si la plupart des variables locales existent dans la pile. Comme cela a été souligné dans quelques commentaires, vous êtes libre d'implémenter un compilateur qui n'utilise même pas de pile ou de tas, mais à la place d'autres mécanismes de stockage (rarement fait, car les piles et les tas sont parfaits pour cela).
Je vais fournir un code C annoté simple pour illustrer tout cela. La meilleure façon d'apprendre est d'exécuter un programme sous un débogueur et d'observer le comportement. Si vous préférez lire python, passez à la fin de la réponse :)
// Alloué statiquement dans le segment de données lors du premier chargement du programme / DLL
// Désalloué à la fermeture du programme / DLL
// portée - accessible de n'importe où dans le code
int someGlobalVariable;
// Alloué statiquement dans le segment de données lors du premier chargement du programme
// Désalloué à la fermeture du programme / DLL
// portée - accessible de n'importe où dans ce fichier de code particulier
static int someStaticVariable;
// "someArgument" est alloué sur la pile chaque fois que MyFunction est appelé
// "someArgument" est désalloué lorsque MyFunction retourne
// scope - accessible uniquement dans MyFunction ()
void MyFunction (int someArgument) {
// Alloué statiquement dans le segment de données lors du premier chargement du programme
// Désalloué à la fermeture du programme / DLL
// scope - accessible uniquement dans MyFunction ()
static int someLocalStaticVariable;
// Alloué sur la pile à chaque appel de MyFunction
// Désalloué lorsque MyFunction revient
// scope - accessible uniquement dans MyFunction ()
int someLocalVariable;
// Un * pointeur * est alloué sur la pile chaque fois que MyFunction est appelé
// Ce pointeur est désalloué lorsque MyFunction retourne
// scope - le pointeur n'est accessible que dans MyFunction ()
int * someDynamicVariable;
// Cette ligne provoque l'allocation d'un espace pour un entier dans le tas
// lorsque cette ligne est exécutée. Notez que ce n'est pas au début de
// l'appel à MyFunction (), comme les variables automatiques
// scope - seul le code dans MyFunction () peut accéder à cet espace
// * via cette variable particulière *.
// Cependant, si vous passez l'adresse ailleurs, ce code
// peut y accéder aussi
someDynamicVariable = new int;
// Cette ligne libère l'espace pour l'entier dans le tas.
// Si nous ne l'écrivions pas, la mémoire serait "perdue".
// Notez une différence fondamentale entre la pile et le tas
// le tas doit être géré. La pile est gérée pour nous.
supprimer someDynamicVariable;
// Dans d'autres cas, au lieu de désallouer cet espace de tas, vous
// peut stocker l'adresse dans un endroit plus permanent pour l'utiliser plus tard.
// Certaines langues s'occupent même de la désallocation pour vous ... mais
// il doit toujours être pris en charge lors de l'exécution par un mécanisme.
// Lorsque la fonction retourne, someArgument, someLocalVariable
// et le pointeur someDynamicVariable sont désalloués.
// L'espace pointé par someDynamicVariable était déjà
// désalloué avant le retour.
revenir;
}
// Notez que someGlobalVariable, someStaticVariable et
// someLocalStaticVariable continue d'exister, et n'est pas
// désalloué jusqu'à ce que le programme se termine.
Un exemple particulièrement frappant de la raison pour laquelle il est important de faire la distinction entre la durée de vie et la portée est qu'une variable peut avoir une portée locale mais une durée de vie statique - par exemple, "someLocalStaticVariable" dans l'exemple de code ci-dessus. De telles variables peuvent rendre nos habitudes de dénomination communes mais informelles très déroutantes. Par exemple, lorsque nous disons «local», nous entendons généralement «variable allouée automatiquement à portée locale» et lorsque nous disons global, nous entendons généralement «variable allouée statiquement à portée globale». Malheureusement, quand il s'agit de "variables attribuées statiquement à portée de fichier", beaucoup de gens disent simplement ... "hein ???".
Certains des choix de syntaxe en C / C ++ aggravent ce problème - par exemple, de nombreuses personnes pensent que les variables globales ne sont pas "statiques" à cause de la syntaxe ci-dessous.
int var1; // A une portée globale et une allocation statique
static int var2; // A une portée de fichier et une allocation statique
int main () {return 0;}
Notez que mettre le mot-clé "static" dans la déclaration ci-dessus empêche var2 d'avoir une portée globale. Néanmoins, la variable globale var1 a une allocation statique. Ce n'est pasintuitif! Pour cette raison, j'essaie de ne jamais utiliser le mot «statique» pour décrire la portée, et plutôt de dire quelque chose comme «fichier» ou «fichier limité». Cependant, de nombreuses personnes utilisent l'expression "statique" ou "portée statique" pour décrire une variable accessible uniquement à partir d'un fichier de code. Dans le contexte de la durée de vie, «statique» signifie toujours que la variable est allouée au démarrage du programme et désallouée lorsque le programme se termine.
Certaines personnes considèrent ces concepts comme spécifiques à C / C ++. Ils ne sont pas. Par exemple, l'exemple Python ci-dessous illustre les trois types d'allocation (il existe des différences subtiles possibles dans les langages interprétés que je n'entrerai pas ici).
à partir de date / heure importation de date / heure
classe Animal:
_FavoriteFood = 'Undefined' # _FavoriteFood est alloué statiquement
def PetAnimal (soi):
curTime = datetime.time (datetime.now ()) # curTime est automatiquement alloué
print ("Merci de m'avoir caressé. Mais c'est" + str (curTime) + ", tu devrais me nourrir. Mon plat préféré est" + self._FavoriteFood)
classe Chat (Animal):
_FavoriteFood = 'tuna'.
Classe Chien (Animal):
_FavoriteFood = 'steak' # De même, la classe Dog obtient sa propre variable statique. Important à noter - cette variable statique est partagée entre toutes les instances de Dog, elle n'est donc pas dynamique!
si __name__ == "__main__":
whiskers = Cat () # Alloué dynamiquement
fido = Dog () # Alloué dynamiquement
rinTinTin = Dog () # Alloué dynamiquement
moustaches.PetAnimal ()
fido.PetAnimal ()
rinTinTin.PetAnimal ()
Dog._FavoriteFood = 'arêtes de lait'
moustaches.PetAnimal ()
fido.PetAnimal ()
rinTinTin.PetAnimal ()
# La sortie est:
# Merci de me caresser. Mais il est 13: 05: 02.255000, vous devriez me nourrir. Mon plat préféré est le thon
# Merci de me caresser. Mais il est 13: 05: 02.255000, vous devriez me nourrir. Mon plat préféré est le steak
# Merci de me caresser. Mais il est 13: 05: 02.255000, vous devriez me nourrir. Mon plat préféré est le steak
# Merci de me caresser. Mais il est 13: 05: 02.255000, vous devriez me nourrir. Mon plat préféré est le thon
# Merci de me caresser. Mais il est 13: 05: 02.255000, vous devriez me nourrir. Ma nourriture préférée est les arêtes de lait
# Merci de me caresser. Mais il est 13: 05: 02.256000, vous devriez me nourrir. Ma nourriture préférée est les arêtes de lait
|
D'autres ont assez bien répondu aux grandes lignes, je vais donc apporter quelques détails.
La pile et le tas n'ont pas besoin d'être singuliers. Une situation courante dans laquelle vous avez plusieurs piles est si vous avez plus d'un thread dans un processus. Dans ce cas, chaque thread a sa propre pile. Vous pouvez également avoir plus d'un tas, par exemple, certaines configurations de DLL peuvent entraîner l'allocation de différentes DLL à partir de différents tas, c'est pourquoi il est généralement préférable de libérer de la mémoire allouée par une bibliothèque différente.
En C, vous pouvez bénéficier de l'allocation de longueur variable grâce à l'utilisation de alloca, qui alloue sur la pile, par opposition à alloc, qui alloue sur le tas. Cette mémoire ne survivra pas à votre déclaration de retour, mais elle est utile pour un tampon de travail.
Créer un énorme tampon temporaire sur Windows que vous n'utilisez pas beaucoup n'est pas gratuit. En effet, le compilateur générera une boucle de sonde de pile qui est appelée à chaque fois que votre fonction est entrée pour s'assurer que la pile existe (car Windows utilise une seule page de garde à la fin de votre pile pour détecter quand il a besoin de développer la pile. Si vous accédez à la mémoire à plus d'une page de la fin de la pile, vous planterez). Exemple:
annuler ma fonction ()
{
char grand [10000000];
// Faites quelque chose qui n'utilise que le premier 1K du gros 99% du temps.
}
|
D'autres ont directement répondu à votre question, mais en essayant de comprendre la pile et le tas, je pense qu'il est utile de considérer la disposition de la mémoire d'un processus UNIX traditionnel (sans threads et allocateurs basés sur mmap ()). La page Web Glossaire de la gestion de la mémoire présente un diagramme de cette disposition de la mémoire.
La pile et le tas sont traditionnellement situés aux extrémités opposées de l'espace d'adressage virtuel du processus. La pile grandit automatiquement lors de l'accès, jusqu'à une taille définie par le noyau (qui peut être ajustée avec setrlimit (RLIMIT_STACK, ...)). Le tas augmente lorsque l'allocateur de mémoire appelle l'appel système brk () ou sbrk (), mappant plus de pages de mémoire physique dans l'espace d'adressage virtuel du processus.
Dans les systèmes sans mémoire virtuelle, comme certains systèmes embarqués, la même disposition de base s'applique souvent, sauf que la pile et le tas sont de taille fixe. Cependant, dans d'autres systèmes embarqués (tels que ceux basés sur des microcontrôleurs Microchip PIC), la pile de programmes est un bloc de mémoire séparé qui n'est pas adressable par des instructions de mouvement de données, et ne peut être modifiée ou lue qu'indirectement via des instructions de flux de programme (appel, retour, etc.). D'autres architectures, telles que les processeurs Intel Itanium, ont plusieurs piles. En ce sens, la pile est un élément de l'architecture CPU.
|
La pile est une portionde mémoire qui peut être manipulée via plusieurs instructions clés en langage d'assemblage, telles que 'pop' (supprimer et renvoyer une valeur de la pile) et 'push' (pousser une valeur dans la pile), mais aussi appeler (appeler un sous-programme - ce pousse l'adresse pour revenir à la pile) et retour (retour d'un sous-programme - cela fait sortir l'adresse de la pile et y saute). C'est la région de la mémoire sous le registre du pointeur de pile, qui peut être définie selon les besoins. La pile est également utilisée pour passer des arguments aux sous-programmes, et aussi pour conserver les valeurs dans les registres avant d'appeler des sous-programmes.
Le tas est une partie de la mémoire donnée à une application par le système d'exploitation, généralement via un appel système comme malloc. Sur les systèmes d'exploitation modernes, cette mémoire est un ensemble de pages auxquelles seul le processus appelant a accès.
La taille de la pile est déterminée au moment de l'exécution et n'augmente généralement pas après le lancement du programme. Dans un programme C, la pile doit être suffisamment grande pour contenir toutes les variables déclarées dans chaque fonction. Le tas augmentera dynamiquement selon les besoins, mais le système d'exploitation finit par faire l'appel (il augmentera souvent le tas de plus que la valeur demandée par malloc, de sorte qu'au moins certains futurs mallocs n'auront pas besoin de retourner au noyau pour obtenir plus de mémoire. Ce comportement est souvent personnalisable)
Parce que vous avez alloué la pile avant de lancer le programme, vous n'avez jamais besoin de malloc avant de pouvoir utiliser la pile, c'est donc un léger avantage. En pratique, il est très difficile de prédire ce qui sera rapide et ce qui sera lent dans les systèmes d'exploitation modernes qui ont des sous-systèmes de mémoire virtuelle, car la manière dont les pages sont implémentées et où elles sont stockées est un détail d'implémentation.
|
Qu'est-ce qu'une pile?
Une pile est une pile d'objets, généralement soigneusement agencés.
Les piles dans les architectures informatiques sont des régions de mémoire dans lesquelles les données sont ajoutées ou supprimées selon le principe du dernier entré, premier sorti.
Dans une application multi-thread, chaque thread aura sa propre pile.
Qu'est-ce qu'un tas?
Un tas est une collection désordonnée de choses empilées au hasard.
Dans les architectures informatiques, le tas est une zone de mémoire allouée dynamiquement qui est gérée automatiquement par le système d'exploitation ou la bibliothèque du gestionnaire de mémoire.
La mémoire sur le tas est allouée, désallouée et redimensionnée régulièrement pendant l'exécution du programme, ce qui peut conduire à un problème appelé fragmentation.
La fragmentation se produit lorsque des objets mémoire sont alloués avec de petits espaces entre eux qui sont trop petits pour contenir des objets mémoire supplémentaires.
Le résultat net est un pourcentage de l'espace de tas qui n'est pas utilisable pour d'autres allocations de mémoire.
Les deux ensemble
Dans une application multi-thread, chaque thread aura sa propre pile. Mais, tous les différents threads partageront le tas.
Étant donné que les différents threads partagent le tas dans une application multi-thread, cela signifie également qu'il doit y avoir une certaine coordination entre les threads afin qu'ils n'essaient pas d'accéder et de manipuler le ou les mêmes morceaux de mémoire dans le tas le même temps.
Qu'est-ce qui est le plus rapide - la pile ou le tas? Et pourquoi?
La pile est beaucoup plus rapide que le tas.
Cela est dû à la façon dont la mémoire est allouée sur la pile.
L'allocation de mémoire sur la pile est aussi simple que de déplacer le pointeur de pile vers le haut.
Pour les novices en programmation, c'est probablement une bonne idée d'utiliser la pile car c'est plus facile.
Étant donné que la pile est petite, vous voudrez l'utiliser lorsque vous savez exactement combien de mémoire vous aurez besoin pour vos données, ou si vous savez que la taille de vos données est très petite.
Il est préférable d’utiliser le tas lorsque vous savez que vous aurez besoin de beaucoup de mémoire pour vos données, ou que vous n’êtes tout simplement pas sûr de la quantité de mémoire dont vous aurez besoin (comme avec un tableau dynamique).
Modèle de mémoire Java
La pile est la zone de mémoire où les variables locales (y compris les paramètres de méthode) sont stockées. Quand il s'agit de variables d'objet, ce ne sont que des références (pointeurs) aux objets réels sur le tas.
Chaque fois qu'un objet est instancié, une partie de la mémoire du tas est mise de côté pour contenir les données (état) de cet objet. Les objets pouvant contenir d'autres objets, certaines de ces données peuvent en fait contenir des références à ces objets imbriqués.
|
Je pense que de nombreuses autres personnes vous ont donné des réponses pour la plupart correctes à ce sujet.
Cependant, un détail qui a été oublié est que le "tas" devrait en fait s'appeler probablement le "magasin gratuit". La raison de cette distinction est que le magasin libre d'origine a été implémenté avec une structure de données connue sous le nom de «tas binomial». Pour cette raison, l'allocation à partir des premières implémentations de malloc () / free () était une allocation à partir d'un tas. Cependant, de nos jours, la plupart des magasins gratuits sont implémentés avec des structures de données très élaborées qui ne sont pas des tas binomiaux.
|
Vous pouvez faire des choses intéressantes avec la pile. Par exemple, vous avez des fonctions comme alloca (en supposant que vous puissiez passer les nombreux avertissements concernant son utilisation), qui est une forme de malloc quiutilise spécifiquement la pile, et non le tas, pour la mémoire.
Cela dit, les erreurs de mémoire basées sur la pile sont parmi les pires que j'ai connues. Si vous utilisez la mémoire de tas et que vous dépassez les limites de votre bloc alloué, vous avez une chance décente de déclencher une erreur de segment. (Pas à 100%: votre bloc peut être accidentellement contigu à un autre que vous avez précédemment alloué.) Mais comme les variables créées sur la pile sont toujours contiguës les unes aux autres, l'écriture hors limites peut changer la valeur d'une autre variable. J'ai appris que chaque fois que je sens que mon programme a cessé d'obéir aux lois de la logique, c'est probablement un débordement de tampon.
|
Simplement, la pile est l'endroit où les variables locales sont créées. De plus, chaque fois que vous appelez un sous-programme, le compteur de programme (pointeur vers l'instruction machine suivante) et tous les registres importants, et parfois les paramètres sont poussés sur la pile. Ensuite, toutes les variables locales à l'intérieur du sous-programme sont poussées sur la pile (et utilisées à partir de là). Lorsque le sous-programme se termine, tout ce truc est renvoyé de la pile. Les données du PC et du registre sont récupérées et remises là où elles étaient lorsqu'elles sont apparues, afin que votre programme puisse continuer son chemin.
Le tas est la zone de mémoire à partir de laquelle les allocations de mémoire dynamique sont faites (appels explicites "new" ou "allocate"). Il s'agit d'une structure de données spéciale qui permet de suivre les blocs de mémoire de différentes tailles et leur état d'allocation.
Dans les systèmes "classiques", la RAM était disposée de telle sorte que le pointeur de pile commençait au bas de la mémoire, le pointeur de tas commençait en haut, et ils grandissaient l'un vers l'autre. S'ils se chevauchent, vous n'avez plus de RAM. Cela ne fonctionne pas avec les systèmes d'exploitation multi-thread modernes. Chaque thread doit avoir sa propre pile, et celles-ci peuvent être créées de manière dynamique.
|
De WikiAnwser.
Empiler
Lorsqu'une fonction ou une méthode appelle une autre fonction qui à son tour appelle une autre fonction, etc., l'exécution de toutes ces fonctions reste suspendue jusqu'à ce que la toute dernière fonction retourne sa valeur.
Cette chaîne d'appels de fonction suspendus est la pile, car les éléments de la pile (appels de fonction) dépendent les uns des autres.
La pile est importante à prendre en compte dans la gestion des exceptions et les exécutions de thread.
Tas
Le tas est simplement la mémoire utilisée par les programmes pour stocker les variables.
Les éléments du tas (variables) n'ont pas de dépendances les uns avec les autres et sont toujours accessibles de manière aléatoire à tout moment.
|
Empiler
Accès très rapide
Pas besoin de désallouer explicitement les variables
L'espace est géré efficacement par le processeur, la mémoire ne sera pas fragmentée
Variables locales uniquement
Limite de la taille de la pile (en fonction du système d'exploitation)
Les variables ne peuvent pas être redimensionnées
Tas
Les variables sont accessibles globalement
Aucune limite sur la taille de la mémoire
Accès (relativement) plus lent
Aucune utilisation efficace de l'espace garantie, la mémoire peut se fragmenter au fil du temps lorsque des blocs de mémoire sont alloués, puis libérés
Vous devez gérer la mémoire (vous êtes en charge d'allouer et de libérer des variables)
Les variables peuvent être redimensionnées en utilisant realloc ()
|
En bref
Une pile est utilisée pour l'allocation de mémoire statique et un tas pour l'allocation de mémoire dynamique, tous deux stockés dans la RAM de l'ordinateur.
En détail
La pile
La pile est une structure de données "LIFO" (dernier entré, premier sorti), qui est gérée et optimisée par le CPU assez étroitement. Chaque fois qu'une fonction déclare une nouvelle variable, elle est "poussée" sur la pile. Puis chaque fois qu'une fonction se termine, toutes les variables poussées sur la pile par cette fonction sont libérées (c'est-à-dire qu'elles sont supprimées). Une fois qu'une variable de pile est libérée, cette région de mémoire devient disponible pour d'autres variables de pile.
L'avantage d'utiliser la pile pour stocker des variables, c'est que la mémoire est gérée pour vous. Vous n'avez pas besoin d'allouer de la mémoire à la main ou de la libérer une fois que vous n'en avez plus besoin. De plus, comme le processeur organise la mémoire de la pile de manière très efficace, la lecture et l'écriture des variables de la pile sont très rapides.
Pour en savoir plus, cliquez ici.
Le tas
Le tas est une région de la mémoire de votre ordinateur qui n'est pas gérée automatiquement pour vous et qui n'est pas aussi étroitement gérée par le processeur. C'est une région de mémoire plus flottante (et plus grande). Pour allouer de la mémoire sur le tas, vous devez utiliser malloc () ou calloc (), qui sont des fonctions C intégrées. Une fois que vous avez alloué de la mémoire sur le tas, vous êtes responsable d'utiliser free () pour désallouer cette mémoire lorsque vous n'en avez plus besoin.
Si vous ne parvenez pas à le faire, votre programme aura ce que l'on appelle une fuite de mémoire. Autrement dit, la mémoire sur le tas sera toujours mise de côté (et ne sera pas disponible pour les autres processus). Comme nous le verrons dans la section de débogage, il existe un outil appelé Valgrind qui peut vous aider à détecter les fuites de mémoire.
Contrairement à la pile, le tas n'a pas de restrictions de taille sur la taille variable (à part les limitations physiques évidentes de votre ordinateur). La mémoire du tas est légèrement plus lente à lire et à écrire, car il faut utiliser des pointeurs pour accéder à la mémoire du tas. Nous parlerons bientôt des pointeurs.
Contrairement à la pile,les variables créées sur le tas sont accessibles par n'importe quelle fonction, n'importe où dans votre programme. Les variables de tas ont une portée essentiellement globale.
Pour en savoir plus, cliquez ici.
Les variables allouées sur la pile sont stockées directement dans la mémoire et l'accès à cette mémoire est très rapide, et son allocation est traitée lors de la compilation du programme. Lorsqu'une fonction ou une méthode appelle une autre fonction qui à son tour appelle une autre fonction, etc., l'exécution de toutes ces fonctions reste suspendue jusqu'à ce que la toute dernière fonction retourne sa valeur. La pile est toujours réservée dans un ordre LIFO, le bloc le plus récemment réservé est toujours le prochain bloc à libérer. Cela rend très simple le suivi de la pile, libérer un bloc de la pile n'est rien de plus que d'ajuster un pointeur.
Les variables allouées sur le tas ont leur mémoire allouée au moment de l'exécution et l'accès à cette mémoire est un peu plus lent, mais la taille du tas n'est limitée que par la taille de la mémoire virtuelle. Les éléments du tas n'ont pas de dépendances les uns avec les autres et sont toujours accessibles de manière aléatoire à tout moment. Vous pouvez allouer un bloc à tout moment et le libérer à tout moment. Cela rend beaucoup plus complexe le suivi des parties du tas allouées ou libres à un moment donné.
Vous pouvez utiliser la pile si vous savez exactement combien de données vous devez allouer avant la compilation et qu'elle n'est pas trop volumineuse. Vous pouvez utiliser le tas si vous ne savez pas exactement combien de données vous aurez besoin au moment de l'exécution ou si vous devez allouer beaucoup de données.
Dans une situation multithread, chaque thread aura sa propre pile complètement indépendante, mais ils partageront le tas. La pile est spécifique au thread et le tas est spécifique à l'application. La pile est importante à prendre en compte dans la gestion des exceptions et les exécutions de thread.
Chaque thread obtient une pile, alors qu'il n'y a généralement qu'un seul tas pour l'application (bien qu'il ne soit pas rare d'avoir plusieurs tas pour différents types d'allocation).
Au moment de l'exécution, si l'application a besoin de plus de tas, elle peut allouer de la mémoire à partir de la mémoire libre et si la pile a besoin de mémoire, elle peut allouer de la mémoire à partir de la mémoire allouée à l'application.
Même, plus de détails sont donnés ici et ici.
Maintenant, venez aux réponses à votre question.
Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou l'environnement d'exécution du langage?
Le système d'exploitation alloue la pile pour chaque thread de niveau système lorsque le thread est créé. En général, le système d'exploitation est appelé par le moteur d'exécution du langage pour allouer le segment de mémoire à l'application.
Pour en savoir plus, cliquez ici.
Quelle est leur portée?
Déjà donné en haut.
"Vous pouvez utiliser la pile si vous savez exactement combien de données vous devez allouer avant la compilation, et que ce n’est pas trop gros. Vous pouvez utiliser le tas si vous ne savez pas exactement combien de données vous aurez besoin au moment de l’exécution ou vous devez allouer beaucoup de données. "
Pour en savoir plus, cliquez ici.
Qu'est-ce qui détermine la taille de chacun d'eux?
La taille de la pile est définie par le système d'exploitation lors de la création d'un thread. La taille du tas est définie au démarrage de l'application, mais elle peut augmenter en fonction de l'espace nécessaire (l'allocateur demande plus de mémoire au système d'exploitation).
Qu'est-ce qui rend un plus rapide?
L'allocation de pile est beaucoup plus rapide car elle ne fait vraiment que déplacer le pointeur de pile. En utilisant des pools de mémoire, vous pouvez obtenir des performances comparables de l'allocation de tas, mais cela vient avec une légère complexité supplémentaire et ses propres maux de tête.
En outre, la pile par rapport au tas n'est pas seulement une considération de performances; il vous en dit aussi beaucoup sur la durée de vie attendue des objets.
Les détails peuvent être trouvés ici.
|
OK, simplement et en bref, cela veut dire commandé et non commandé ...!
Pile: Dans les éléments de pile, les choses se superposent, ce qui signifie que le traitement sera plus rapide et plus efficace! ...
Il y a donc toujours un index pour pointer l'élément spécifique, le traitement sera également plus rapide, il y a aussi une relation entre les éléments! ...
Heap: Pas d'ordre, le traitement va être plus lent et les valeurs sont mélangées sans ordre ou index spécifique ... il y a des aléas et il n'y a pas de relation entre eux ... donc l'exécution et le temps d'utilisation peuvent varier ...
Je crée également l'image ci-dessous pour montrer à quoi ils peuvent ressembler:
|
pile, tas et données de chaque processus dans la mémoire virtuelle:
|
Dans les années 1980, UNIX s'est propagé comme des lapins avec de grandes entreprises qui roulaient les leurs.
Exxon en avait un, tout comme des dizaines de marques perdues dans l'histoire.
La façon dont la mémoire était disposée était à la discrétion des nombreux développeurs.
Un programme C typique a été mis à plat en mémoire avec
une opportunité d'augmenter en changeant la valeur de brk ().
En règle générale, le HEAP était juste en dessous de cette valeur brk
et l'augmentation de brk a augmenté la quantité de tas disponible.
Le STACK unique était généralement une zone sous HEAP qui était une étendue de mémoire
ne contenant rien de valeur jusqu'au début du bloc de mémoire fixe suivant.
Ce bloc suivant était souvent CODE qui pouvait être écrasé par les données de la pile
dans l'un des célèbres hacks de son époque.
Un bloc de mémoire typique était BSS (un bloc de zérovaleurs)
qui n'a pas été accidentellement mis à zéro dans l'offre d'un fabricant.
Un autre était DATA contenant des valeurs initialisées, y compris des chaînes et des nombres.
Un troisième était CODE contenant CRT (Runtime C), main, fonctions et bibliothèques.
L'avènement de la mémoire virtuelle sous UNIX modifie de nombreuses contraintes.
Il n'y a aucune raison objective pour laquelle ces blocs doivent être contigus,
ou fixé en taille, ou commandé d'une manière particulière maintenant.
Bien sûr, avant UNIX, il y avait Multics qui ne souffrait pas de ces contraintes.
Voici un schéma montrant l'un des schémas de mémoire de cette époque.
|
Quelques centimes: je pense, il sera bon de dessiner une mémoire graphique et plus simple:
Flèches - montrent où croître la pile et le tas, la taille de la pile de processus a une limite, définie dans le système d'exploitation, les limites de taille de la pile de threads par paramètres dans l'API de création de threads généralement. Heap généralement limité par la taille maximale de la mémoire virtuelle du processus, pour 32 bits 2-4 Go par exemple.
Manière si simple: le tas de processus est général pour le processus et tous les threads à l'intérieur, en utilisant pour l'allocation de mémoire dans le cas courant avec quelque chose comme malloc ().
Stack est une mémoire rapide pour stocker dans les pointeurs et variables de retour de fonction de cas communs, traités comme des paramètres dans l'appel de fonction, des variables de fonction locales.
|
Étant donné que certaines réponses sont devenues pinailleuses, je vais apporter mon acarien.
Étonnamment, personne n'a mentionné que plusieurs piles d'appels (c'est-à-dire non liées au nombre de threads en cours d'exécution au niveau du système d'exploitation) se trouvent non seulement dans des langages exotiques (PostScript) ou des plates-formes (Intel Itanium), mais aussi dans des fibres, des threads verts et quelques implémentations de coroutines.
Les fibres, les fils verts et les coroutines sont similaires à bien des égards, ce qui conduit à beaucoup de confusion. La différence entre les fibres et les fils verts est que les premiers utilisent le multitâche coopératif, tandis que les seconds peuvent en présenter un coopératif ou préemptif (ou même les deux). Pour la distinction entre fibres et coroutines, voir ici.
Dans tous les cas, le but des deux fibres, threads verts et coroutines est d'avoir plusieurs fonctions s'exécutant simultanément, mais pas en parallèle (voir cette question SO pour la distinction) dans un seul thread au niveau du système d'exploitation, en transférant le contrôle dans les deux sens. de manière organisée.
Lorsque vous utilisez des fibres, des fils verts ou des coroutines, vous disposez généralement d'une pile distincte par fonction. (Techniquement, pas seulement une pile mais tout un contexte d'exécution est par fonction. Plus important encore, les registres du processeur.) Pour chaque thread, il y a autant de piles que de fonctions exécutées simultanément, et le thread bascule entre l'exécution de chaque fonction selon la logique de votre programme. Lorsqu'une fonction arrive à sa fin, sa pile est détruite. Ainsi, le nombre et la durée de vie des piles sont dynamiques et ne sont pas déterminés par le nombre de threads au niveau du système d'exploitation!
Notez que j'ai dit "avoir généralement une pile séparée par fonction". Il existe à la fois des implémentations empilables et sans pile de couroutines. Les implémentations C ++ empilables les plus notables sont Boost.Coroutine et async / await de Microsoft PPL. (Cependant, les fonctions resumables de C ++ (a.k.a. "async and await"), qui ont été proposées à C ++ 17, sont susceptibles d'utiliser des coroutines sans pile.)
La proposition de Fibers à la bibliothèque standard C ++ est à venir. En outre, il existe des bibliothèques tierces. Les fils verts sont extrêmement populaires dans des langages comme Python et Ruby.
|
J'ai quelque chose à partager, même si les principaux points sont déjà abordés.
Empiler
Accès très rapide.
Stocké dans la RAM.
Les appels de fonction sont chargés ici avec les variables locales et les paramètres de fonction passés.
L'espace est libéré automatiquement lorsque le programme sort de sa portée.
Stocké en mémoire séquentielle.
Tas
Accès lent comparativement à Stack.
Stocké dans la RAM.
Les variables créées dynamiquement sont stockées ici, ce qui nécessite plus tard de libérer la mémoire allouée après utilisation.
Stocké partout où l'allocation de mémoire est effectuée, toujours accessible par le pointeur.
Note intéressante:
Si les appels de fonction avaient été stockés dans le tas, cela aurait entraîné 2 points désordonnés:
En raison du stockage séquentiel dans la pile, l'exécution est plus rapide. Le stockage en tas aurait entraîné une consommation de temps énorme, rendant ainsi l'exécution du programme tout entier plus lente.
Si les fonctions étaient stockées dans le tas (stockage désordonné pointé par le pointeur), il n'y aurait eu aucun moyen de revenir à l'adresse de l'appelant (que la pile donne en raison du stockage séquentiel en mémoire).
|
Hou la la! Tant de réponses et je ne pense pas que l'une d'elles ait bien compris ...
1) Où et quels sont-ils (physiquement dans la mémoire d'un vrai ordinateur)?
La pile est la mémoire qui commence par l'adresse mémoire la plus élevée allouée à votre image de programme, puis sa valeur diminue à partir de là. Il est réservé aux paramètres des fonctions appelées et à toutes les variables temporaires utilisées dans les fonctions.
Il y a deux tas: public et privé.
Le tas privé commence sur une limite de 16 octets (pour les programmes 64 bits) ou une limite de 8 octets (pour les programmes 32 bits) après le dernier octet de code de votre programme, puis augmentevaleur à partir de là. Il est également appelé le tas par défaut.
Si le tas privé devient trop grand, il chevauchera la zone de pile, de même que la pile chevauchera le tas s'il devient trop gros. Parce que la pile commence à une adresse plus élevée et descend vers une adresse inférieure, avec un piratage approprié, vous pouvez obtenir une pile si grande qu'elle dépassera la zone de tas privée et chevauchera la zone de code. L'astuce consiste alors à chevaucher suffisamment la zone de code que vous pouvez accrocher au code. C'est un peu délicat à faire et vous risquez un plantage du programme, mais c'est facile et très efficace.
Le tas public réside dans son propre espace mémoire en dehors de l'espace image de votre programme. C'est cette mémoire qui sera siphonnée sur le disque dur si les ressources mémoire se raréfient.
2) Dans quelle mesure sont-ils contrôlés par le système d'exploitation ou l'environnement d'exécution du langage?
La pile est contrôlée par le programmeur, le tas privé est géré par le système d'exploitation et le tas public n'est contrôlé par personne car il s'agit d'un service du système d'exploitation - vous faites des demandes et elles sont accordées ou refusées.
2b) Quelle est leur portée?
Ils sont tous globaux pour le programme, mais leur contenu peut être privé, public ou mondial.
2c) Qu'est-ce qui détermine la taille de chacun d'eux?
La taille de la pile et du tas privé sont déterminés par les options d'exécution de votre compilateur. Le tas public est initialisé au moment de l'exécution à l'aide d'un paramètre de taille.
2d) Qu'est-ce qui le rend plus rapide?
Ils ne sont pas conçus pour être rapides, ils sont conçus pour être utiles. La façon dont le programmeur les utilise détermine si elles sont «rapides» ou «lentes»
REF:
https://norasandler.com/2019/02/18/Write-a-Compiler-10.html
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate
|
Beaucoup de réponses sont correctes en tant que concepts, mais il faut noter qu'une pile est nécessaire au matériel (c'est-à-dire au microprocesseur) pour permettre l'appel de sous-programmes (CALL en langage assembleur ..). (Les gars de la POO l'appelleront méthodes)
Sur la pile, vous enregistrez les adresses de retour et l'appel → push / ret → pop est géré directement dans le matériel.
Vous pouvez utiliser la pile pour passer des paramètres ... même si c'est plus lent que d'utiliser des registres (dirait un gourou du microprocesseur ou un bon livre BIOS des années 1980 ...)
Sans pile, aucun microprocesseur ne peut fonctionner. (on ne peut pas imaginer un programme, même en langage assembleur, sans sous-programmes / fonctions)
Sans le tas qu'il peut. (Un programme en langage assembleur peut fonctionner sans, car le tas est un concept de système d'exploitation, comme malloc, c'est-à-dire un appel OS / Lib.
L'utilisation de la pile est plus rapide car:
Est-ce que le matériel, et même push / pop sont très efficaces.
malloc nécessite d'entrer en mode noyau, d'utiliser lock / semaphore (ou d'autres primitives de synchronisation) en exécutant du code et de gérer certaines structures nécessaires pour garder une trace de l'allocation.
|
Le tas est une zone de mémoire allouée dynamiquement qui est gérée automatiquement par le système d'exploitation ou la bibliothèque du gestionnaire de mémoire. Vous pouvez allouer un bloc à tout moment et le libérer à tout moment. L'allocation de tas nécessite de conserver un enregistrement complet de la mémoire allouée et de ce qui ne l'est pas, ainsi que de la maintenance des frais généraux pour réduire la fragmentation, trouver des segments de mémoire contigus suffisamment grands pour s'adapter à la taille demandée, etc. La mémoire peut être désallouée à tout moment en laissant de l'espace libre. Au fur et à mesure que le tas se développe, de nouveaux blocs sont souvent alloués des adresses inférieures vers les adresses supérieures. Ainsi, vous pouvez considérer le tas comme un tas de blocs de mémoire dont la taille augmente à mesure que la mémoire est allouée. Si le tas est trop petit pour une allocation, la taille peut souvent être augmentée en acquérant plus de mémoire du système d'exploitation sous-jacent. La mémoire allouée à partir du tas restera allouée jusqu'à ce que l'un des événements suivants se produise:
La mémoire est libérée
Le programme se termine
Empiler:
Stocké dans la RAM de l'ordinateur, tout comme le tas.
Les variables créées sur la pile sortiront du champ d'application et seront automatiquement désallouées.
Beaucoup plus rapide à allouer par rapport aux variables sur le tas.
Stocke les données locales, les adresses de retour, utilisées pour le passage des paramètres.
Peut avoir un débordement de pile lorsqu'une trop grande partie de la pile est utilisée (principalement
récursion infinie ou trop profonde, allocations très importantes).
Vous utiliseriez la pile si vous savez exactement combien de données vous avez besoin
allouer avant la compilation et ce n'est pas trop gros.
A généralement une taille maximale déjà déterminée lorsque votre programme
départs.
Tas:
Stocké dans la RAM de l'ordinateur tout comme la pile.
En C ++, les variables sur le tas doivent être détruites manuellement et jamais
tomber hors de portée.
Les données sont libérées avec delete, delete [] ou free.
Plus lent à allouer par rapport aux variables de la pile.
Utilisé à la demande pour allouer un bloc de données à utiliser par le programme.
Peut avoir une fragmentation lorsqu'il y a beaucoup d'allocations et
désallocations.
En C ++ ou C, les données créées sur le tas seront pointées par des pointeurs
et alloué avec new ou malloc respectivement.
Peut avoir des échecs d'allocation si une mémoire tampon trop grande est demandée
être attribué.
Toiutiliserait le tas si vous ne savez pas exactement combien de données vous
aura besoin au moment de l'exécution ou si vous avez besoin d'allouer beaucoup de données.
Responsable des fuites de mémoire.
|
La pile est essentiellement une mémoire facile d'accès qui gère simplement ses éléments
comme une pile - bien -. Seuls les objets dont la taille est connue à l'avance peuvent être placés dans la pile. C'est le cas des nombres, des chaînes, des booléens.
Le tas est une mémoire pour les éléments dont vous ne pouvez pas prédéterminer le
taille et structure exactes. Étant donné que les objets et les tableaux peuvent être mutés et
changer au moment de l'exécution, ils doivent aller dans le tas.
Source: Academind
|
La pile et le tas du processeur sont physiquement liés à la façon dont le processeur et les registres fonctionnent avec la mémoire, au fonctionnement du langage d'assemblage de machine, et non aux langages de haut niveau eux-mêmes, même si ces langages peuvent décider de petites choses.
Tous les processeurs modernes fonctionnent avec la «même» théorie des microprocesseurs: ils sont tous basés sur ce qu'on appelle des «registres» et certains sont destinés à «stack» pour gagner en performances. Tous les processeurs ont des registres de pile depuis le début et ils avaient toujours été là, façon de parler, comme je le sais. Les langages d'assemblage sont les mêmes depuis le début, malgré des variations ... jusqu'à Microsoft et son langage intermédiaire (IL) qui ont changé le paradigme pour avoir un langage d'assemblage de machine virtuelle OO. Nous pourrons donc avoir des CPU CLI / CIL dans le futur (un projet de MS).
Les processeurs ont des registres de pile pour accélérer l'accès aux mémoires, mais ils sont limités par rapport à l'utilisation d'autres registres pour obtenir un accès complet à toute la mémoire disponible pour le processus. C'est pourquoi nous avons parlé d'allocations de pile et de tas.
En résumé, et en général, le tas est lourd et lent et est destiné aux instances et objets "globaux", car la pile est petite et rapide et aux variables et références "locales" (pointeurs cachés pour oublier de les gérer).
Ainsi, lorsque nous utilisons le nouveau mot-clé dans une méthode, la référence (un int) est créée dans la pile, mais l'objet et tout son contenu (types de valeur ainsi que objets) sont créés dans le tas, si je me souviens bien. Mais les types de valeur élémentaires locaux et les tableaux sont créés dans la pile.
La différence d'accès mémoire se situe au niveau du référencement des cellules: l'adressage du tas, la mémoire globale du processus, demande plus de complexité en termes de gestion des registres CPU, que la pile qui est "plus" localement en termes d'adressage car la pile CPU register est utilisé comme adresse de base, si je me souviens bien.
C'est pourquoi lorsque nous avons des appels ou des boucles très longs ou infinis, nous avons rapidement un débordement de pile, sans geler le système sur les ordinateurs modernes ...
C # Heap (ing) vs Stack (ing) dans .NET
Stack vs Heap: connaître la différence
Allocation de mémoire de classe statique où elle est stockée C #
Quoi et où sont la pile et le tas?
https://en.wikipedia.org/wiki/Memory_management
https://en.wikipedia.org/wiki/Stack_register
Ressources du langage d'assemblage:
Tutoriel de programmation d'assemblage
Manuels du développeur de logiciels pour architectures Intel® 64 et IA-32
|
Merci pour une très bonne discussion mais en tant que vrai noob, je me demande où sont conservées les instructions? Au DEBUT, les scientifiques décidaient entre deux architectures (von NEUMANN où tout est considéré DATA et HARVARD où une zone de mémoire était réservée aux instructions et une autre aux données). En fin de compte, nous avons opté pour le design von Neumann et maintenant tout est considéré comme «pareil». Cela m'a rendu la tâche difficile lorsque j'apprenais l'assemblage
https://www.cs.virginia.edu/~evans/cs216/guides/x86.html
car ils parlent de registres et de pointeurs de pile.
Tout ce qui précède parle de DATA. Je suppose que comme une instruction est une chose définie avec une empreinte mémoire spécifique, elle irait sur la pile et donc tous 'ces' registres discutés dans l'assemblage sont sur la pile. Bien sûr, est venue la programmation orientée objet avec des instructions et des données réunies dans une structure dynamique, de sorte que maintenant les instructions seraient également conservées sur le tas?
|
Question très active. Gagnez 10 points de réputation pour répondre à cette question. L'exigence de réputation permet de protéger cette question contre les spams et les activités sans réponse.
Ce n'est pas la réponse que vous recherchez? Parcourez les autres questions marquées avec allocation de mémoire dynamique de pile de gestion de la mémoire ou posez votre propre question.